/*
 * Copyright 2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import * as _ from "lodash"
import { PageList } from "../data"

import httpClient from "../httpclient"
import { OptionSelection, OptionSelections } from "./selection"

export enum ProductType {
  Physical = "physical",
  Digital = "digital",
}

export enum ProductStatus {
  Active = "active",
  Archived = "archived",
  Pending = "pending",
  Disapproved = "disapproved",
}

export enum InventoryStatus {
  InStock = "in_stock",
  LimitedStock = "limited_stock",
  OutOfStock = "out_of_stock",
}

export class ProductOptionValue {
  public id: string
  public label: string
  public position: number
}

export class ProductOption {
  public id: string
  public name: string
  public values: ProductOptionValue[] = []
  public position: number
}

export class ProductVariant {
  public id: string
  public weight: number
  public width: number
  public height: number
  public depth: number
  public price: number
  private inventory_quantity: number
  private option_selections: OptionSelection[] = []
  private image_urls: string[] = []
  public position?: number

  public get optionSelections() {
    return this.option_selections
  }

  public set optionSelections(selections) {
    this.option_selections = selections
  }

  public get imageUrls() {
    return this.image_urls
  }

  public set imageUrls(urls) {
    this.image_urls = urls
  }

  public get inventoryQuantity() {
    return this.inventory_quantity
  }

  public set inventoryQuantity(quantity) {
    this.inventory_quantity = quantity
  }

  public static assign(variant: any): ProductVariant {
    return _.assign(new ProductVariant(), variant, {
      optionSelections: _.map(_.get(variant, "option_selections"), (selection: any) => _.assign(new OptionSelection(), selection)),
    })
  }
}

export class ProductAttribute {
  public namespace: string
  public name: string
  public value: string
}

export class ProductOrigin {
  public province_id: string = ""
  public province: string = ""
  public city_id: string = ""
  public city: string = ""
  public county_id: string = ""
  public county: string = ""

  public get provinceId() {
    return this.province_id
  }

  public set provinceId(provinceId: string) {
    this.province_id = provinceId
  }

  public get cityId() {
    return this.city_id
  }

  public set cityId(cityId: string) {
    this.city_id = cityId
  }

  public get countyId() {
    return this.county_id
  }

  public set countyId(countyId: string) {
    this.county_id = countyId
  }

}

export class Product {
  public id: string
  private store_id: string
  public name: string
  public type: ProductType
  public status: ProductStatus
  public description: string
  public options: ProductOption[] = []
  public variants: ProductVariant[] = []
  public attributes: ProductAttribute[] = []
  private image_urls: string[] = []
  public collections: string[] = []
  private total_sales: number
  private monthly_sales: number
  public price: number
  private inventory_status: InventoryStatus
  private inventory_quantity: number
  private free_shipping: boolean
  private fixed_shipping_cost: number
  private shipping_rate_id: string
  public origin: ProductOrigin
  private created_time: Date
  public body: string
  private raw_body: string

  public get rawBody() {
    return this.raw_body
  }

  public set rawBody(rawBody) {
    this.raw_body = rawBody
  }

  public get totalSales() {
    return this.total_sales
  }

  public get monthlySales() {
    return this.monthly_sales
  }

  public get inventoryStatus() {
    return this.inventory_status
  }

  public set inventoryStatus(status) {
    this.inventory_status = status
  }

  public get inventoryQuantity() {
    return this.inventory_quantity
  }

  public set inventoryQuantity(quantity) {
    this.inventory_quantity = quantity
  }

  public get storeId() {
    return this.store_id
  }

  public set storeId(value) {
    this.store_id = value
  }

  public get imageUrls() {
    return this.image_urls
  }

  public set imageUrls(urls) {
    this.image_urls = urls
  }

  public get fixedShippingCost() {
    return this.fixed_shipping_cost
  }

  public set fixedShippingCost(fixedShippingCost) {
    this.fixed_shipping_cost = fixedShippingCost
  }

  public get shippingRateId() {
    return this.shipping_rate_id
  }

  public set shippingRateId(shippingRateId) {
    this.shipping_rate_id = shippingRateId
  }

  public set freeShipping(freeShipping) {
    this.free_shipping = freeShipping
  }

  public get freeShipping() {
    return this.free_shipping
  }

  public get createdTime() {
    return this.created_time
  }

  public get firstVariant(): ProductVariant {
    return _.minBy(this.variants, variant => variant.position) as ProductVariant
  }

  public get firstImageUrl(): string {
    return this.image_urls[0]
  }

  public getDefaultOptions() {
    const { options } = this
    return _.map(options, option => _.minBy(option.values, "position")?.label)
  }

  public getVariant(variantId: string): ProductVariant {
    const findVariant = _.find(this.variants, variant => _.isEqual(variant.id, variantId))
    return findVariant ? findVariant : this.firstVariant
  }

  public selectVariant(selections: OptionSelection[]): ProductVariant | undefined {
    return _.find(this.variants, variant => OptionSelections.equals(variant.optionSelections, selections))
  }

  public addVariant(variant: ProductVariant) {
    const aVariant = this.selectVariant(variant.optionSelections)
    if (_.isUndefined(aVariant)) {
      this.variants.push(variant)
    } else {
      _.assign(aVariant, variant)
    }
  }

  public addVariants(variants: ProductVariant[]) {
    _.forEach(variants, variant => this.addVariant(variant))
  }

  public clearVariants() {
    this.variants = []
  }

  public static assign(product: any, ...args: any): Product {
    return _.assign(new Product(), product, {
      variants: _.map(_.get(product, "variants"), ProductVariant.assign),
      attributes: _.map(_.get(product, "attributes"), assignProductAttribute),
    }, ...args)
  }
}

function assignProductAttribute(attribute: any): Product {
  return _.assign(new ProductAttribute(), attribute)
}

class ProductQuery {
  private params = {}

  public page(page: number) {
    this.params["page"] = page
    return this
  }

  public limit(limit: number) {
    this.params["limit"] = limit
    return this
  }

  public sort(sort?: string) {
    if (_.isEmpty(sort)) {
      _.unset(this.sort, "sort")
    } else {
      this.params["sort"] = sort
    }
    return this
  }

  public name(name?: string) {
    if (_.isEmpty(name)) {
      _.unset(this.params, "name")
    } else {
      this.params["name"] = name
    }
    return this
  }

  public collections(collections?: string[] | string) {
    if (_.isEmpty(collections)) {
      _.unset(this.params, "collections")
    } else {
      this.params["collections"] = collections
    }
    return this
  }

  public types(types?: ProductType[] | string) {
    if (_.isEmpty(types)) {
      _.unset(this.params, "types")
    } else {
      this.params["types"] = types
    }
    return this
  }

  public statuses(statuses?: ProductStatus[] | string) {
    if (_.isEmpty(statuses)) {
      _.unset(this.params, "statuses")
    } else {
      this.params["statuses"] = statuses
    }
    return this
  }

  public inventoryStatuses(statuses?: InventoryStatus[] | string) {
    if (_.isEmpty(statuses)) {
      _.unset(this.params, "inventory_statuses")
    } else {
      this.params["inventory_statuses"] = statuses
    }
    return this
  }

  public storeId(storeId?: string) {
    if (_.isEmpty(storeId)) {
      _.unset(this.params, "store_id")
    } else {
      this.params["store_id"] = storeId
    }
    return this
  }

  public minPrice(minPrice?: string) {
    if (minPrice) {
      this.params["min_price"] = minPrice
    } else {
      _.unset(this.params, "min_price")
    }
    return this
  }

  public maxPrice(maxPrice?: string) {
    if (maxPrice) {
      this.params["max_price"] = maxPrice
    } else {
      _.unset(this.params, "max_price")
    }
    return this
  }

  public list(): Promise<PageList<Product>> {
    return httpClient
      .get(`/v1/products`, { params: this.params })
      .then(({ data }) => {
        return _.assign(PageList.empty(), data, {
          elements: _.map(data.elements, Product.assign),
        })
      })
  }

  public count(): Promise<number> {
    return httpClient
      .get(`/v1/products/count`, { params: this.params })
      .then(({ data }) => data)
  }
}


/**
 * Product service Api V1
 */
export class ProductServiceV1 {

  public static createProduct(product: Product): Promise<Product> {
    return httpClient.post(`/v1/products`, product)
      .then(({ data }) => data)
      .then(Product.assign)
  }

  public static updateProduct(product: Product): Promise<Product> {
    return httpClient.patch(`/v1/products/${product.id}`, product)
      .then(({ data }) => data)
      .then(Product.assign)
  }

  public static publishProduct(id: any): Promise<void> {
    return httpClient.post(`/v1/products/${id}/publish`)
  }

  public static unpublishProduct(id: any): Promise<void> {
    return httpClient.delete(`/v1/products/${id}/publish`)
  }

  public static deleteProduct(id: any): Promise<void> {
    return httpClient.delete(`/v1/products/${id}`)
  }

  public static getProduct(id: any): Promise<Product> {
    return httpClient
      .get(`/v1/products/${id}`)
      .then(({ data }) => data)
      .then(Product.assign)
  }

  public static createProductQuery(): ProductQuery {
    return new ProductQuery()
  }

  public static getProducts(): ProductQuery {
    return new ProductQuery()
  }
}
